package com.farm.wsch.index.impl;

import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;

import com.farm.util.cache.FarmCacheName;
import com.farm.util.cache.FarmCaches;
import com.farm.wsch.index.WschSearchServerInter;
import com.farm.wsch.index.WschIndexServerInter.DefaultField;
import com.farm.wsch.index.pojo.IndexConfig;
import com.farm.wsch.index.pojo.IndexResult;
import com.farm.wsch.index.pojo.ResultDicNum;
import com.farm.wsch.index.pojo.ResultDicNum.PeekType;
import com.farm.wsch.index.pojo.RuleInfo;
import com.farm.wsch.index.pojo.RuntimeInfo;
import com.farm.wsch.index.pojo.SearchRule;

public class WschSearchServerImpl implements WschSearchServerInter {
	private IndexConfig config;
	private List<Query> rules = new ArrayList<>();
	private List<ResultDicNum> dicstats = new ArrayList<>();
	private List<RuleInfo> ruleinfos = new ArrayList<>();
	private Set<String> userPopkeys = new HashSet<>();
	private String sortField = null;
	private String sortType = null;
	// 当在结果集合中查询时，设置该值
	private String resultId = null;

	public WschSearchServerImpl(IndexConfig config) throws IOException {
		this.config = config;
	}

	@Override
	public WschSearchServerInter addRule(SearchRule rule) throws ParseException {
		if (rule != null) {
			return addRule(rule.getVal(), rule.getFieldkey(), rule.isLike(), rule.isSmart());
		} else {
			return this;
		}
	}

	@Override
	public WschSearchServerInter addRule(String value, List<String> titles, Map<String, Float> boots, boolean isSmart)
			throws ParseException {
		if (boots == null) {
			boots = new HashMap<>();
		}
		Analyzer analyzer = isSmart ? new IKAnalyzer(true) : new IKAnalyzer();
		String[] strings = new String[titles.size()];
		QueryParser parser = new MultiFieldQueryParser(titles.toArray(strings), analyzer, boots);
		if (isSmart) {
			String[] arr = value.split("\\s+");
			StringBuffer newVal = new StringBuffer();
			for (String ss : arr) {
				// 双引号括起来的不做分词
				newVal.append("\"" + ss.replace("\"", "") + "\" ");
			}
			value = newVal.toString();
		}
		rules.add(parser.parse(value));

		int n = 0;
		for (String title : titles) {
			RuleInfo info = new RuleInfo();
			if (n == 0) {
				info.setNewGroup(true);
			}
			n++;
			info.setFieldKey(title);
			info.setVal(value);
			info.setSearchFlag(
					"like" + (isSmart ? "-smart" : "") + (boots.get(title) == null ? "x1.0" : "x" + boots.get(title)));
			ruleinfos.add(info);
		}
		return this;
	}

	@Override
	public WschSearchServerInter addRule(String value, String title, boolean isLike, boolean isSmart)
			throws ParseException {
		if (isLike) {
			List<String> titles = new ArrayList<String>();
			titles.add(title);
			return addRule(value, titles, null, isSmart);
		} else {
			TermQuery query = new TermQuery(new Term(title, value));
			rules.add(query);
			{
				RuleInfo info = new RuleInfo();
				info.setNewGroup(true);
				info.setFieldKey(title);
				info.setVal(value);
				info.setSearchFlag("=");
				ruleinfos.add(info);
			}
			return this;
		}
	}

	// ----------------------------------------------------
	@Override
	public IndexResult search(int pagenum, int pagesize) throws IOException {
		IndexResult result = new IndexResult(pagesize);
		BooleanQuery query = null;
		{// 构造查询条件
			BooleanQuery.Builder builder = new BooleanQuery.Builder();
			for (Query node : rules) {
				builder.add(node, Occur.MUST);
			}
			query = builder.build();
		}
		// 第一步：创建一个Directory对象，也就是索引库存放的位置。
		Directory directory = FSDirectory.open(Paths.get(config.getIndexDir().getPath()));
		// 第二步：创建一个indexReader对象，需要指定Directory对象。
		DirectoryReader indexReader = DirectoryReader.open(directory);
		// 第三步：创建一个indexsearcher对象，需要指定IndexReader对象
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		// ------------------------
		// -----------物理检索-------------
		RuntimeInfo timeInfo1 = new RuntimeInfo("索引检索");
		TopDocs topDocs = indexSearcher.search(query, config.getSearchTopNum());
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		result.addRuntimeInfo(timeInfo1, scoreDocs.length);
		// --------------权限过滤/分布统计---------------------------------
		String currentResultid = UUID.randomUUID().toString();
		List<ScoreDoc> userDocs = null;
		{
			RuntimeInfo timeInfo2 = new RuntimeInfo("权限过滤/分布统计");
			// 此处过滤(权限过滤、统计)
			userDocs = runResultFilter(scoreDocs, indexSearcher, currentResultid);
			result.addRuntimeInfo(timeInfo2, userDocs.size());
		}
		// ------------------排序-------------------------------
		{
			// 排序
			sortUserDocs(userDocs, indexSearcher, result);
		}
		int size = userDocs.size();
		int start = (pagenum - 1) * pagesize;
		int end = start + pagesize;
		// -------------------------数据装载-start----------------------------------------
		RuntimeInfo timeInfo3 = new RuntimeInfo("数据装载");
		int loadNum = 0;
		if (start > size || size <= 0) {
			// 没有结果
		} else {
			if (end > size) {
				end = size;
			}
			for (; start < end; start++) {
				ScoreDoc sdoc = userDocs.get(start);
				int doc = sdoc.doc;
				Document document = indexSearcher.doc(doc);
				{
					Map<String, Object> node = new HashMap<String, Object>();
					for (IndexableField field : document.getFields()) {
						String name = field.name();
						String value = document.get(field.name());
						String highValue = getHighLightValue(field.name(), value, query);
						value = value.length() > config.getHighLightLength()
								? value.substring(0, config.getHighLightLength() - 1)
								: value;
						node.put(name + "_HL", highValue == null ? value : highValue);
						node.put(name, value);
					}
					loadNum++;
					result.add(node);
				}
			}
			indexReader.close();
		}
		result.addRuntimeInfo(timeInfo3, loadNum);
		// ----------------------数据装载-end------------------------
		result.setTotalSize(userDocs.size());
		result.setResultId(currentResultid);
		result.setCurrentPage(pagenum);
		result.setRuleinfos(ruleinfos);
		result.initSearchTime();
		return result;
	}

	/**
	 * 对结果进行排序
	 * 
	 * @param userDocs
	 */
	private void sortUserDocs(List<ScoreDoc> userDocs, IndexSearcher indexSearcher, IndexResult result) {
		if (StringUtils.isNotBlank(sortField)) {
			RuntimeInfo timeInfo3 = new RuntimeInfo("结果排序");
			Collections.sort(userDocs, new Comparator<ScoreDoc>() {
				@Override
				public int compare(ScoreDoc o1, ScoreDoc o2) {
					try {
						Document document1 = indexSearcher.doc(o1.doc);
						Document document2 = indexSearcher.doc(o2.doc);
						int re = document1.get(sortField).compareTo(document2.get(sortField));
						if (sortType.equals("ASC")) {
							return re;
						} else {
							return -re;
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
					return 0;
				}
			});
			result.addRuntimeInfo(timeInfo3, userDocs.size());
		}
	}

	/**
	 * 加载高亮
	 * 
	 * @param title
	 * @param text
	 * @return
	 */
	private String getHighLightValue(String title, String text, Query query) {
		try {
			SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(config.getHighLightTagStart(),
					config.getHighLightTagEnd());
			Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
			highlighter.setTextFragmenter(new SimpleFragmenter(config.getHighLightLength()));
			Analyzer analyzer = new IKAnalyzer();
			try {
				TokenStream tokenStream = analyzer.tokenStream(title.toUpperCase(), new StringReader(text));
				try {
					return highlighter.getBestFragment(tokenStream, text);
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					tokenStream.close();
				}
			} finally {
				analyzer.close();
			}
		} catch (InvalidTokenOffsetsException | IOException e) {
			e.printStackTrace();
		}
		return text;
	}

	/**
	 * 过滤权限、统计分布
	 * 
	 * @param scoreDocs
	 * @param indexSearcher
	 * @throws IOException
	 */

	private List<ScoreDoc> runResultFilter(ScoreDoc[] scoreDocs, IndexSearcher indexSearcher, String currentResultid)
			throws IOException {
		// 需要將sco過濾一下，來控制權限
		List<ScoreDoc> list = new ArrayList<>();
		Set<String> ids = new HashSet<String>();
		// -------------------------------
		for (ScoreDoc node : scoreDocs) {
			boolean isAble = true;
			Document document = indexSearcher.doc(node.doc);
			String id = document.get(DefaultField.APPID.getKey());
			String docPopKeys = document.get(DefaultField.POPKEYS.getKey());
			List<String> docPopKeyList = docPopKeys != null ? parseIds(docPopKeys) : new ArrayList<>();
			// 结果中查询
			if (!resultSearchFilter(id)) {
				// 无效结果
				continue;
			}
			if (!isPopAble(docPopKeyList, userPopkeys)) {
				// 无权限
				continue;
			}
			// 分布统计
			statNum(document);
			if (isAble) {
				// 缓存结果集合
				ids.add(id);
				// 添加有效结果
				list.add(node);
			}
		}
		// -------------缓存当前结果集合---------------------------
		FarmCaches.getInstance().putCacheData(currentResultid, ids, FarmCacheName.wschResultIds);
		return list;
	}

	/**
	 * 用户是否有检索权限
	 * 
	 * @param docPopKeyList
	 * @param popkeys2
	 * @return
	 */
	private boolean isPopAble(List<String> docpops, Set<String> userpops) {
		if (userpops.contains("ALLABLE") || docpops.size() <= 0) {
			return true;
		}
		for (String key : docpops) {
			if (userpops.contains(key.trim()) || key.equals("ALLABLE")) {
				return true;
			}
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	private boolean resultSearchFilter(String id) {
		if (resultId != null) {
			// 过滤结果集合中查询
			Set<String> searchIds = (Set<String>) FarmCaches.getInstance().getCacheData(resultId,
					FarmCacheName.wschResultIds);
			if (searchIds == null) {
				searchIds = new HashSet<>();
			}
			if (!searchIds.contains(id)) {
				return false;
			}
			return true;
		} else {
			return true;
		}
	}

	/**
	 * 分布统计
	 * 
	 * @param document
	 */
	private void statNum(Document document) {
		for (IndexableField field : document.getFields()) {
			String name = field.name();
			String value = document.get(field.name());
			for (ResultDicNum dic : dicstats) {
				if (dic.getFieldkey().equals(name) && dic.getValkey() != null && value != null) {
					// 全等模式匹配
					if (dic.getPeektype().equals(PeekType.equals)) {
						if (dic.getValkey().trim().equals(value.trim())) {
							dic.setNum(dic.getNum() + 1);
						}
					}
					// 子串模式匹配
					if (dic.getPeektype().equals(PeekType.indexOfS)) {
						if (value.trim().indexOf(dic.getValkey().trim()) >= 0) {
							dic.setNum(dic.getNum() + 1);
						}
					}
				}
			}
		}
	}

	@Override
	public void setResultDicNums(List<ResultDicNum> dics) {
		if (dics != null) {
			dicstats = dics;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setFilterResultId(String byResultid) {
		if (StringUtils.isBlank(byResultid)) {
			return;
		}
		Set<String> searchIds = (Set<String>) FarmCaches.getInstance().getCacheData(byResultid,
				FarmCacheName.wschResultIds);
		if (searchIds == null) {
			searchIds = new HashSet<>();
		}
		RuleInfo info = new RuleInfo();
		info.setNewGroup(true);
		info.setFieldKey("RESULLT");
		info.setFieldTitle("在" + searchIds.size() + "条结果集中");
		info.setVal("");
		info.setSearchFlag("查询");
		ruleinfos.add(info);
		this.resultId = byResultid;
	}

	@Override
	public void setSearchPopKeys(Set<String> popkeys) {
		userPopkeys.clear();
		userPopkeys.addAll(popkeys);
	}

	private static List<String> parseIds(String ids) {
		if (ids == null) {
			return new ArrayList<String>();
		}
		ids = ids.replace("，", ",");
		String[] markdot = ids.split(",");
		List<String> list_ = new ArrayList<String>();
		for (int i = 0; i < markdot.length; i++) {
			String temp = markdot[i];
			if (temp != null && !temp.equals("") && !temp.equals(" "))
				list_.add(temp);
		}
		return list_;
	}

	@Override
	public void setSort(String fieldkey, String sorttype) {
		if (StringUtils.isNotBlank(fieldkey)) {
			RuleInfo info = new RuleInfo();
			info.setNewGroup(true);
			info.setFieldKey(fieldkey);
			info.setVal(sorttype);
			info.setSearchFlag("sort");
			ruleinfos.add(info);
			this.sortField = fieldkey;
			this.sortType = sorttype.equals("ASC") ? "ASC" : "DESC";
		}
	}
}
